/*
 *  Copyright (C) 2007 Veit Wehner.
 *  All Rights Reserved.
 *
 *  This is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This software is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this software; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
 *  USA.
 */



 /** \file checksum_encryption.c
 * \brief This file contains the cryptographic functions.
 * \author Veit Wehner <veit.wehner@gmail.com>
 */
         

#include "sw_update.h"

/* For both, davies-meyer and TEA, the key is 4*sizeof(long) 
    and the message parts that will be encoded are 2*sizeof(long)
*/
#if (ENCRYPTION == TEA || HASH_CHECKSUM==DAVIES_MEYER)
unsigned long * key;
unsigned long w[2];
unsigned char tmp[KEY_LENGTH*sizeof(unsigned long)];
#endif

#if HASH_CHECKSUM == DAVIES_MEYER 
unsigned long hash[2];
#endif

#if ENCRYPTION == SHA1
unsigned long key[KEY_LENGTH];
SHA1_CTX context;
unsigned char digest[20];
#endif


#if ENCRYPTION || HASH_CHECKSUM
/**
 * This function loads the secret from the infomem.
 * The secret will be used as initial Key for the 
 * encryption or cryptographic hashing. 
 */
void get_key_or_initial_hash_from_infomem(){
  ramcpy_infomem(tmp, sizeof(tmp));
  key = (unsigned long*)tmp;  
  int k;
  for (k = 0; k < sizeof(unsigned long); k++) {
    printf("%lx", ((unsigned long*)key)[k]);
    putchar(' ');
  }
  putchar('\n');
  
}   
#endif  

#if (ENCRYPTION == TEA || HASH_CHECKSUM==DAVIES_MEYER)
/**
 * This function implements the TEA encryption.
 * @param 8 Byte message
 * @param 8 Byte for the encrypted result
 * @param 16 Byte key
 */
void ENC_encrypt( unsigned long *v,
                  unsigned long *w,
                  unsigned long *k)
{
  unsigned long   y =    v[0],
    z =    v[1],
    sum =  0,
    delta= 0x9E3779B9,
    a =    k[0],
    b =    k[1],
    c =    k[2],
    d =    k[3],
    n =    32;
  
  while(n-->0)
    {
      sum += delta;
      y += ((z << 4)+a) ^ (z+sum) ^ ((z >> 5)+b);
      z += ((y << 4)+c) ^ (y+sum) ^ ((y >> 5)+d);
    }
  
  w[0]=y; w[1]=z;
}

/**
 * This function implements the TEA decryption.
 * @param 8 Byte message
 * @param 8 Byte for the decrypted result
 * @param 16 Byte key
 */			
void DEC_decrypt( unsigned long *v,
                  unsigned long *w,
                  unsigned long *k)
{
  unsigned long   y = v[0],
    z = v[1],
    sum = 0xC6EF3720,
    delta = 0x9E3779B9,
    a = k[0],
    b = k[1],
    c = k[2],
    d = k[3],
    n = 32;
  
  while(n-->0)
    {
      z -= ((y << 4)+c) ^ (y+sum) ^ ((y >> 5)+d);
      y -= ((z << 4)+a) ^ (z+sum) ^ ((z >> 5)+b);
      sum -= delta;
    }
  w[0]=y; w[1]=z;
  
}
#endif


#if HASH_CHECKSUM == CRC16
/**
 * This function calculates a new 16 bit CRC. It may be called 
 * in an iteration several times.
 * @param new checksum
 * @param actual checksum resulting from previous calculations
 * @param length of data input
 */
unsigned short crc16(unsigned short crc, const unsigned char *data, unsigned int len)
{
  unsigned int i;
  
  for (i = 0; i < len; i += 2) {
    //printf ("DATA: %02x, CRC: %04x\n", data[i],  crc);        
    unsigned short d = (data[i] << 8) | data[i+1];
    int b;
    for (b = 0; b < 16; b++, d >>= 1)
      if (crc & 0x8000)
	crc = ((crc << 1) | (d & 1)) ^ 0x1021;
      else
	crc = (crc << 1) | (d & 1);
    
  }
  return crc ^ 0xffff;
}
#endif


#if ENCRYPTION == TEA
/**
 * This function encodes a data packet _in_place_.
 * @param YaNETBuffer with packet data and socket information 
 */
void encode_packet(unsigned char *tmp, unsigned char *buf, int len){
  memset(tmp,0,len);
  tmp[0] = len;
  memcpy(tmp+1,buf,len);
  int i;
  for (i=0;i<(CHUNKSIZE/(2*sizeof(unsigned long)));i++){
    ENC_encrypt((unsigned long*)(tmp+i*2*sizeof(unsigned long)), w, key);
    ((unsigned long*)(tmp+i*2*sizeof(unsigned long)))[0] = w[0];
    ((unsigned long*)(tmp+i*2*sizeof(unsigned long)))[1] = w[1];
  }
}


/**
 * This function decodes a data packet _in_place_.
 * @param YaNETBuffer with packet data and socket information 
 */
void decode_packet(struct YaNETBuffer_s *YanetRxBuffer){
  unsigned long v[2];
  int i;
  for (i=0;i<YanetRxBuffer->datalen/(2*sizeof(unsigned long));i++){
    DEC_decrypt((unsigned long*)(YanetRxBuffer->data+i*2*sizeof(unsigned long)), v, key);
    ((unsigned long*)(YanetRxBuffer->data+i*2*sizeof(unsigned long)))[0] = v[0];
    ((unsigned long*)(YanetRxBuffer->data+i*2*sizeof(unsigned long)))[1] = v[1];
  }
  int x;
  YanetRxBuffer->datalen = YanetRxBuffer->data[0];
  for (x=0;x<YanetRxBuffer->datalen;x++){
    YanetRxBuffer->data[x] = YanetRxBuffer->data[x+1];
  }
  
}
#endif



/** This function calculates and appends the cryptographic checksum to the packet 
*   when it is called by the sender. When called by the receiver, it removes and verifies 
*   the signature. When the signature is wrong, it sets the value of the acknwoldment sync 
*   sign to WRONG.
*   @param sw_upd pointer variable to the swupdate instance
*   @param sig_op   enum type instance for either adding or removing a signature
*/

#if HASH_CHECKSUM == DAVIES_MEYER
void signature_packet(struct swupdate *swupd, enum sig_op arg){
  char longs;
  if (arg == remove_sig){
    longs = (swupd->datalen-HASH_LENGTH*sizeof(unsigned long))/(KEY_LENGTH*sizeof(unsigned long));
  }
  else {
    longs = swupd->datalen/(KEY_LENGTH*sizeof(unsigned long));
  }
  
  char j;
  for (j=0;j<longs;j++){
    unsigned long new_hash[2];
    memcpy(key+0, ((unsigned long*)swupd->priv_data_buf)+j*KEY_LENGTH+0, sizeof(unsigned long));
    memcpy(key+1, ((unsigned long*)swupd->priv_data_buf)+j*KEY_LENGTH+1, sizeof(unsigned long));
    memcpy(key+2, ((unsigned long*)swupd->priv_data_buf)+j*KEY_LENGTH+2, sizeof(unsigned long));
    memcpy(key+3, ((unsigned long*)swupd->priv_data_buf)+j*KEY_LENGTH+3, sizeof(unsigned long));
    ENC_encrypt(hash, new_hash, key);
    memcpy(hash, new_hash, sizeof(new_hash));
  }  
  if (arg == sig){
    SHOW(signing);
    memcpy(swupd->priv_data_buf+swupd->datalen,hash,sizeof(unsigned long)*HASH_LENGTH);
    swupd->datalen += sizeof(unsigned long)*HASH_LENGTH;
  }
  else if (arg == remove_sig){
    SHOW(removing signature);
    unsigned long packet_signature[2];
    memcpy(packet_signature, swupd->priv_data_buf+(swupd->datalen-sizeof(unsigned long)*HASH_LENGTH), sizeof(unsigned long)*HASH_LENGTH);
    swupd->datalen -= sizeof(unsigned long)*HASH_LENGTH;
    if (memcmp(packet_signature, hash, sizeof(unsigned long)*HASH_LENGTH) != 0){
      swupd->communice->acknowledgment = WRONG;
      swupd->communice->packet_loss_count++;
    }   
    else {
      swupd->communice->acknowledgment = VERIFIED;
      swupd->communice->packet_loss_count = 0;
    }
    SHOW_DEC(swupd->communice->acknowledgment);
  }
  
}
#endif
